home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / PEAR / Downloader.php < prev    next >
PHP Script  |  2004-10-01  |  23KB  |  660 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 5                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available through the world-wide-web at the following url:           |
  11. // | http://www.php.net/license/3_0.txt.                                  |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stig Bakken <ssb@php.net>                                   |
  17. // |          Tomas V.V.Cox <cox@idecnet.com>                             |
  18. // |          Martin Jansen <mj@php.net>                                  |
  19. // +----------------------------------------------------------------------+
  20. //
  21. // $Id: Downloader.php,v 1.14 2004/02/29 15:58:17 avsm Exp $
  22.  
  23. require_once 'PEAR/Common.php';
  24. require_once 'PEAR/Registry.php';
  25. require_once 'PEAR/Dependency.php';
  26. require_once 'PEAR/Remote.php';
  27. require_once 'System.php';
  28.  
  29. /**
  30.  * Administration class used to download PEAR packages and maintain the
  31.  * installed package database.
  32.  *
  33.  * @since PEAR 1.4
  34.  * @author Greg Beaver <cellog@php.net>
  35.  */
  36. class PEAR_Downloader extends PEAR_Common
  37. {
  38.     /**
  39.      * @var PEAR_Config
  40.      * @access private
  41.      */
  42.     var $_config;
  43.  
  44.     /**
  45.      * @var PEAR_Registry
  46.      * @access private
  47.      */
  48.     var $_registry;
  49.  
  50.     /**
  51.      * @var PEAR_Remote
  52.      * @access private
  53.      */
  54.     var $_remote;
  55.  
  56.     /**
  57.      * Preferred Installation State (snapshot, devel, alpha, beta, stable)
  58.      * @var string|null
  59.      * @access private
  60.      */
  61.     var $_preferredState;
  62.  
  63.     /**
  64.      * Options from command-line passed to Install.
  65.      * 
  66.      * Recognized options:<br />
  67.      *  - onlyreqdeps   : install all required dependencies as well
  68.      *  - alldeps       : install all dependencies, including optional
  69.      *  - installroot   : base relative path to install files in
  70.      *  - force         : force a download even if warnings would prevent it
  71.      * @see PEAR_Command_Install
  72.      * @access private
  73.      * @var array
  74.      */
  75.     var $_options;
  76.  
  77.     /**
  78.      * Downloaded Packages after a call to download().
  79.      * 
  80.      * Format of each entry:
  81.      *
  82.      * <code>
  83.      * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  84.      *    'info' => array() // parsed package.xml
  85.      * );
  86.      * </code>
  87.      * @access private
  88.      * @var array
  89.      */
  90.     var $_downloadedPackages = array();
  91.  
  92.     /**
  93.      * Packages slated for download.
  94.      * 
  95.      * This is used to prevent downloading a package more than once should it be a dependency
  96.      * for two packages to be installed.
  97.      * Format of each entry:
  98.      *
  99.      * <pre>
  100.      * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
  101.      * );
  102.      * </pre>
  103.      * @access private
  104.      * @var array
  105.      */
  106.     var $_toDownload = array();
  107.     
  108.     /**
  109.      * Array of every package installed, with names lower-cased.
  110.      * 
  111.      * Format:
  112.      * <code>
  113.      * array('package1' => 0, 'package2' => 1, );
  114.      * </code>
  115.      * @var array
  116.      */
  117.     var $_installed = array();
  118.     
  119.     /**
  120.      * @var array
  121.      * @access private
  122.      */
  123.     var $_errorStack = array();
  124.  
  125.     // {{{ PEAR_Downloader()
  126.     
  127.     function PEAR_Downloader(&$ui, $options, &$config)
  128.     {
  129.         $this->_options = $options;
  130.         $this->_config = &$config;
  131.         $this->_preferredState = $this->_config->get('preferred_state');
  132.         $this->ui = &$ui;
  133.         if (!$this->_preferredState) {
  134.             // don't inadvertantly use a non-set preferred_state
  135.             $this->_preferredState = null;
  136.         }
  137.  
  138.         $php_dir = $this->_config->get('php_dir');
  139.         if (isset($this->_options['installroot'])) {
  140.             if (substr($this->_options['installroot'], -1) == DIRECTORY_SEPARATOR) {
  141.                 $this->_options['installroot'] = substr($this->_options['installroot'], 0, -1);
  142.             }
  143.             $php_dir = $this->_prependPath($php_dir, $this->_options['installroot']);
  144.         }
  145.         $this->_registry = &new PEAR_Registry($php_dir);
  146.         $this->_remote = &new PEAR_Remote($config);
  147.  
  148.         if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
  149.             $this->_installed = $this->_registry->listPackages();
  150.             array_walk($this->_installed, create_function('&$v,$k','$v = strtolower($v);'));
  151.             $this->_installed = array_flip($this->_installed);
  152.         }
  153.         parent::PEAR_Common();
  154.     }
  155.     
  156.     // }}}
  157.     // {{{ configSet()
  158.     function configSet($key, $value, $layer = 'user')
  159.     {
  160.         $this->_config->set($key, $value, $layer);
  161.         $this->_preferredState = $this->_config->get('preferred_state');
  162.         if (!$this->_preferredState) {
  163.             // don't inadvertantly use a non-set preferred_state
  164.             $this->_preferredState = null;
  165.         }
  166.     }
  167.     
  168.     // }}}
  169.     // {{{ setOptions()
  170.     function setOptions($options)
  171.     {
  172.         $this->_options = $options;
  173.     }
  174.     
  175.     // }}}
  176.     // {{{ _downloadFile()
  177.     /**
  178.      * @param string filename to download
  179.      * @param string version/state
  180.      * @param string original value passed to command-line
  181.      * @param string|null preferred state (snapshot/devel/alpha/beta/stable)
  182.      *                    Defaults to configuration preferred state
  183.      * @return null|PEAR_Error|string
  184.      * @access private
  185.      */
  186.     function _downloadFile($pkgfile, $version, $origpkgfile, $state = null)
  187.     {
  188.         if (is_null($state)) {
  189.             $state = $this->_preferredState;
  190.         }
  191.         // {{{ check the package filename, and whether it's already installed
  192.         $need_download = false;
  193.         if (preg_match('#^(http|ftp)://#', $pkgfile)) {
  194.             $need_download = true;
  195.         } elseif (!@is_file($pkgfile)) {
  196.             if ($this->validPackageName($pkgfile)) {
  197.                 if ($this->_registry->packageExists($pkgfile)) {
  198.                     if (empty($this->_options['upgrade']) && empty($this->_options['force'])) {
  199.                         $errors[] = "$pkgfile already installed";
  200.                         return;
  201.                     }
  202.                 }
  203.                 $pkgfile = $this->getPackageDownloadUrl($pkgfile, $version);
  204.                 $need_download = true;
  205.             } else {
  206.                 if (strlen($pkgfile)) {
  207.                     $errors[] = "Could not open the package file: $pkgfile";
  208.                 } else {
  209.                     $errors[] = "No package file given";
  210.                 }
  211.                 return;
  212.             }
  213.         }
  214.         // }}}
  215.  
  216.         // {{{ Download package -----------------------------------------------
  217.         if ($need_download) {
  218.             $downloaddir = $this->_config->get('download_dir');
  219.             if (empty($downloaddir)) {
  220.                 if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
  221.                     return $downloaddir;
  222.                 }
  223.                 $this->log(3, '+ tmp dir created at ' . $downloaddir);
  224.             }
  225.             $callback = $this->ui ? array(&$this, '_downloadCallback') : null;
  226.             $this->pushErrorHandling(PEAR_ERROR_RETURN);
  227.             $file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback);
  228.             $this->popErrorHandling();
  229.             if (PEAR::isError($file)) {
  230.                 if ($this->validPackageName($origpkgfile)) {
  231.                     if (!PEAR::isError($info = $this->_remote->call('package.info',
  232.                           $origpkgfile))) {
  233.                         if (!count($info['releases'])) {
  234.                             return $this->raiseError('Package ' . $origpkgfile .
  235.                             ' has no releases');
  236.                         } else {
  237.                             return $this->raiseError('No releases of preferred state "'
  238.                             . $state . '" exist for package ' . $origpkgfile .
  239.                             '.  Use ' . $origpkgfile . '-state to install another' .
  240.                             ' state (like ' . $origpkgfile .'-beta)',
  241.                             PEAR_INSTALLER_ERROR_NO_PREF_STATE);
  242.                         }
  243.                     } else {
  244.                         return $pkgfile;
  245.                     }
  246.                 } else {
  247.                     return $this->raiseError($file);
  248.                 }
  249.             }
  250.             $pkgfile = $file;
  251.         }
  252.         // }}}
  253.         return $pkgfile;
  254.     }
  255.     // }}}
  256.     // {{{ getPackageDownloadUrl()
  257.  
  258.     function getPackageDownloadUrl($package, $version = null)
  259.     {
  260.         if ($version) {
  261.             $package .= "-$version";
  262.         }
  263.         if ($this === null || $this->_config === null) {
  264.             $package = "http://pear.php.net/get/$package";
  265.         } else {
  266.             $package = "http://" . $this->_config->get('master_server') .
  267.                 "/get/$package";
  268.         }
  269.         if (!extension_loaded("zlib")) {
  270.             $package .= '?uncompress=yes';
  271.         }
  272.         return $package;
  273.     }
  274.  
  275.     // }}}
  276.     // {{{ extractDownloadFileName($pkgfile, &$version)
  277.  
  278.     function extractDownloadFileName($pkgfile, &$version)
  279.     {
  280.         if (@is_file($pkgfile)) {
  281.             return $pkgfile;
  282.         }
  283.         // regex defined in Common.php
  284.         if (preg_match(PEAR_COMMON_PACKAGE_DOWNLOAD_PREG, $pkgfile, $m)) {
  285.             $version = (isset($m[3])) ? $m[3] : null;
  286.             return $m[1];
  287.         }
  288.         $version = null;
  289.         return $pkgfile;
  290.     }
  291.  
  292.     // }}}
  293.  
  294.     // }}}
  295.     // {{{ getDownloadedPackages()
  296.  
  297.     /**
  298.      * Retrieve a list of downloaded packages after a call to {@link download()}.
  299.      * 
  300.      * Also resets the list of downloaded packages.
  301.      * @return array
  302.      */
  303.     function getDownloadedPackages()
  304.     {
  305.         $ret = $this->_downloadedPackages;
  306.         $this->_downloadedPackages = array();
  307.         $this->_toDownload = array();
  308.         return $ret;
  309.     }
  310.  
  311.     // }}}
  312.     // {{{ download()
  313.  
  314.     /**
  315.      * Download any files and their dependencies, if necessary
  316.      *
  317.      * BC-compatible method name
  318.      * @param array a mixed list of package names, local files, or package.xml
  319.      */
  320.     function download($packages)
  321.     {
  322.         return $this->doDownload($packages);
  323.     }
  324.     
  325.     // }}}
  326.     // {{{ doDownload()
  327.  
  328.     /**
  329.      * Download any files and their dependencies, if necessary
  330.      *
  331.      * @param array a mixed list of package names, local files, or package.xml
  332.      */
  333.     function doDownload($packages)
  334.     {
  335.         $mywillinstall = array();
  336.         $state = $this->_preferredState;
  337.  
  338.         // {{{ download files in this list if necessary
  339.         foreach($packages as $pkgfile) {
  340.             $need_download = false;
  341.             if (!is_file($pkgfile)) {
  342.                 if (preg_match('#^(http|ftp)://#', $pkgfile)) {
  343.                     $need_download = true;
  344.                 }
  345.                 $pkgfile = $this->_downloadNonFile($pkgfile);
  346.                 if (PEAR::isError($pkgfile)) {
  347.                     return $pkgfile;
  348.                 }
  349.                 if ($pkgfile === false) {
  350.                     continue;
  351.                 }
  352.             } // end is_file()
  353.  
  354.             $tempinfo = $this->infoFromAny($pkgfile);
  355.             if ($need_download) {
  356.                 $this->_toDownload[] = $tempinfo['package'];
  357.             }
  358.             if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
  359.                 // ignore dependencies if there are any errors
  360.                 if (!PEAR::isError($tempinfo)) {
  361.                     $mywillinstall[strtolower($tempinfo['package'])] = @$tempinfo['release_deps'];
  362.                 }
  363.             }
  364.             $this->_downloadedPackages[] = array('pkg' => $tempinfo['package'],
  365.                                        'file' => $pkgfile, 'info' => $tempinfo);
  366.         } // end foreach($packages)
  367.         // }}}
  368.  
  369.         // {{{ extract dependencies from downloaded files and then download
  370.         // them if necessary
  371.         if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
  372.             $deppackages = array();
  373.             foreach ($mywillinstall as $package => $alldeps) {
  374.                 if (!is_array($alldeps)) {
  375.                     // there are no dependencies
  376.                     continue;
  377.                 }
  378.                 foreach($alldeps as $info) {
  379.                     if ($info['type'] != 'pkg') {
  380.                         continue;
  381.                     }
  382.                     $ret = $this->_processDependency($package, $info, $mywillinstall);
  383.                     if ($ret === false) {
  384.                         continue;
  385.                     }
  386.                     if (PEAR::isError($ret)) {
  387.                         return $ret;
  388.                     }
  389.                     $deppackages[] = $ret;
  390.                 } // foreach($alldeps
  391.             }
  392.  
  393.             if (count($deppackages)) {
  394.                 $this->doDownload($deppackages);
  395.             }
  396.         } // }}} if --alldeps or --onlyreqdeps
  397.     }
  398.  
  399.     // }}}
  400.     // {{{ _downloadNonFile($pkgfile)
  401.     
  402.     /**
  403.      * @return false|PEAR_Error|string false if loop should be broken out of,
  404.      *                                 string if the file was downloaded,
  405.      *                                 PEAR_Error on exception
  406.      * @access private
  407.      */
  408.     function _downloadNonFile($pkgfile)
  409.     {
  410.         $origpkgfile = $pkgfile;
  411.         $state = null;
  412.         $pkgfile = $this->extractDownloadFileName($pkgfile, $version);
  413.         if (preg_match('#^(http|ftp)://#', $pkgfile)) {
  414.             return $this->_downloadFile($pkgfile, $version, $origpkgfile);
  415.         }
  416.         if (!$this->validPackageName($pkgfile)) {
  417.             return $this->raiseError("Package name '$pkgfile' not valid");
  418.         }
  419.         // ignore packages that are installed unless we are upgrading
  420.         $curinfo = $this->_registry->packageInfo($pkgfile);
  421.         if ($this->_registry->packageExists($pkgfile)
  422.               && empty($this->_options['upgrade']) && empty($this->_options['force'])) {
  423.             $this->log(0, "Package '{$curinfo['package']}' already installed, skipping");
  424.             return false;
  425.         }
  426.         if (in_array($pkgfile, $this->_toDownload)) {
  427.             return false;
  428.         }
  429.         $releases = $this->_remote->call('package.info', $pkgfile, 'releases', true);
  430.         if (!count($releases)) {
  431.             return $this->raiseError("No releases found for package '$pkgfile'");
  432.         }
  433.         // Want a specific version/state
  434.         if ($version !== null) {
  435.             // Passed Foo-1.2
  436.             if ($this->validPackageVersion($version)) {
  437.                 if (!isset($releases[$version])) {
  438.                     return $this->raiseError("No release with version '$version' found for '$pkgfile'");
  439.                 }
  440.             // Passed Foo-alpha
  441.             } elseif (in_array($version, $this->getReleaseStates())) {
  442.                 $state = $version;
  443.                 $version = 0;
  444.                 foreach ($releases as $ver => $inf) {
  445.                     if ($inf['state'] == $state && version_compare("$version", "$ver") < 0) {
  446.                         $version = $ver;
  447.                         break;
  448.                     }
  449.                 }
  450.                 if ($version == 0) {
  451.                     return $this->raiseError("No release with state '$state' found for '$pkgfile'");
  452.                 }
  453.             // invalid postfix passed
  454.             } else {
  455.                 return $this->raiseError("Invalid postfix '-$version', be sure to pass a valid PEAR ".
  456.                                          "version number or release state");
  457.             }
  458.         // Guess what to download
  459.         } else {
  460.             $states = $this->betterStates($this->_preferredState, true);
  461.             $possible = false;
  462.             $version = 0;
  463.             foreach ($releases as $ver => $inf) {
  464.                 if (in_array($inf['state'], $states) && version_compare("$version", "$ver") < 0) {
  465.                     $version = $ver;
  466.                     break;
  467.                 }
  468.             }
  469.             if ($version === 0 && !isset($this->_options['force'])) {
  470.                 return $this->raiseError('No release with state equal to: \'' . implode(', ', $states) .
  471.                                          "' found for '$pkgfile'");
  472.             } elseif ($version === 0) {
  473.                 $this->log(0, "Warning: $pkgfile is state '$inf[state]' which is less stable " .
  474.                               "than state '$this->_preferredState'");
  475.             }
  476.         }
  477.         // Check if we haven't already the version
  478.         if (empty($this->_options['force']) && !is_null($curinfo)) {
  479.             if ($curinfo['version'] == $version) {
  480.                 $this->log(0, "Package '{$curinfo['package']}-{$curinfo['version']}' already installed, skipping");
  481.                 return false;
  482.             } elseif (version_compare("$version", "{$curinfo['version']}") < 0) {
  483.                 $this->log(0, "Package '{$curinfo['package']}' version '{$curinfo['version']}' " .
  484.                               " is installed and {$curinfo['version']} is > requested '$version', skipping");
  485.                 return false;
  486.             }
  487.         }
  488.         $this->_toDownload[] = $pkgfile;
  489.         return $this->_downloadFile($pkgfile, $version, $origpkgfile, $state);
  490.     }
  491.     
  492.     // }}}
  493.     // {{{ _processDependency($package, $info, $mywillinstall)
  494.     
  495.     /**
  496.      * Process a dependency, download if necessary
  497.      * @param array dependency information from PEAR_Remote call
  498.      * @param array packages that will be installed in this iteration
  499.      * @return false|string|PEAR_Error
  500.      * @access private
  501.      * @todo Add test for relation 'lt'/'le' -> make sure that the dependency requested is
  502.      *       in fact lower than the required value.  This will be very important for BC dependencies
  503.      */
  504.     function _processDependency($package, $info, $mywillinstall)
  505.     {
  506.         $state = $this->_preferredState;
  507.         if (!isset($this->_options['alldeps']) && isset($info['optional']) &&
  508.               $info['optional'] == 'yes') {
  509.             // skip optional deps
  510.             $this->log(0, "skipping Package '$package' optional dependency '$info[name]'");
  511.             return false;
  512.         }
  513.         // {{{ get releases
  514.         $releases = $this->_remote->call('package.info', $info['name'], 'releases', true);
  515.         if (PEAR::isError($releases)) {
  516.             return $releases;
  517.         }
  518.         if (!count($releases)) {
  519.             if (!isset($this->_installed[strtolower($info['name'])])) {
  520.                 $this->pushError("Package '$package' dependency '$info[name]' ".
  521.                             "has no releases");
  522.             }
  523.             return false;
  524.         }
  525.         $found = false;
  526.         $save = $releases;
  527.         while(count($releases) && !$found) {
  528.             if (!empty($state) && $state != 'any') {
  529.                 list($release_version, $release) = each($releases);
  530.                 if ($state != $release['state'] &&
  531.                     !in_array($release['state'], $this->betterStates($state)))
  532.                 {
  533.                     // drop this release - it ain't stable enough
  534.                     array_shift($releases);
  535.                 } else {
  536.                     $found = true;
  537.                 }
  538.             } else {
  539.                 $found = true;
  540.             }
  541.         }
  542.         if (!count($releases) && !$found) {
  543.             $get = array();
  544.             foreach($save as $release) {
  545.                 $get = array_merge($get,
  546.                     $this->betterStates($release['state'], true));
  547.             }
  548.             $savestate = array_shift($get);
  549.             $this->pushError( "Release for '$package' dependency '$info[name]' " .
  550.                 "has state '$savestate', requires '$state'");
  551.             return false;
  552.         }
  553.         if (in_array(strtolower($info['name']), $this->_toDownload) ||
  554.               isset($mywillinstall[strtolower($info['name'])])) {
  555.             // skip upgrade check for packages we will install
  556.             return false;
  557.         }
  558.         if (!isset($this->_installed[strtolower($info['name'])])) {
  559.             // check to see if we can install the specific version required
  560.             if ($info['rel'] == 'eq') {
  561.                 return $info['name'] . '-' . $info['version'];
  562.             }
  563.             // skip upgrade check for packages we don't have installed
  564.             return $info['name'];
  565.         }
  566.         // }}}
  567.  
  568.         // {{{ see if a dependency must be upgraded
  569.         $inst_version = $this->_registry->packageInfo($info['name'], 'version');
  570.         if (!isset($info['version'])) {
  571.             // this is a rel='has' dependency, check against latest
  572.             if (version_compare($release_version, $inst_version, 'le')) {
  573.                 return false;
  574.             } else {
  575.                 return $info['name'];
  576.             }
  577.         }
  578.         if (version_compare($info['version'], $inst_version, 'le')) {
  579.             // installed version is up-to-date
  580.             return false;
  581.         }
  582.         return $info['name'];
  583.     }
  584.     
  585.     // }}}
  586.     // {{{ _downloadCallback()
  587.  
  588.     function _downloadCallback($msg, $params = null)
  589.     {
  590.         switch ($msg) {
  591.             case 'saveas':
  592.                 $this->log(1, "downloading $params ...");
  593.                 break;
  594.             case 'done':
  595.                 $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
  596.                 break;
  597.             case 'bytesread':
  598.                 static $bytes;
  599.                 if (empty($bytes)) {
  600.                     $bytes = 0;
  601.                 }
  602.                 if (!($bytes % 10240)) {
  603.                     $this->log(1, '.', false);
  604.                 }
  605.                 $bytes += $params;
  606.                 break;
  607.             case 'start':
  608.                 $this->log(1, "Starting to download {$params[0]} (".number_format($params[1], 0, '', ',')." bytes)");
  609.                 break;
  610.         }
  611.         if (method_exists($this->ui, '_downloadCallback'))
  612.             $this->ui->_downloadCallback($msg, $params);
  613.     }
  614.  
  615.     // }}}
  616.     // {{{ _prependPath($path, $prepend)
  617.  
  618.     function _prependPath($path, $prepend)
  619.     {
  620.         if (strlen($prepend) > 0) {
  621.             if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
  622.                 $path = $prepend . substr($path, 2);
  623.             } else {
  624.                 $path = $prepend . $path;
  625.             }
  626.         }
  627.         return $path;
  628.     }
  629.     // }}}
  630.     // {{{ pushError($errmsg, $code)
  631.     
  632.     /**
  633.      * @param string
  634.      * @param integer
  635.      */
  636.     function pushError($errmsg, $code = -1)
  637.     {
  638.         array_push($this->_errorStack, array($errmsg, $code));
  639.     }
  640.     
  641.     // }}}
  642.     // {{{ getErrorMsgs()
  643.     
  644.     function getErrorMsgs()
  645.     {
  646.         $msgs = array();
  647.         $errs = $this->_errorStack;
  648.         foreach ($errs as $err) {
  649.             $msgs[] = $err[0];
  650.         }
  651.         $this->_errorStack = array();
  652.         return $msgs;
  653.     }
  654.     
  655.     // }}}
  656. }
  657. // }}}
  658.  
  659. ?>
  660.